package com.zhike.service.im.manager;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ConcurrentMap;

import javax.annotation.Resource;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.zhike.common.dto.CMDBody4RevokeMessageRequest;
import com.zhike.common.dto.CMDBodyReadedMessageRequest;
import com.zhike.common.dto.GroupAtUserForSessionDTO;
import com.zhike.common.dto.RevokedDTO;
import com.zhike.common.dto.UserProtocalsType;
import com.zhike.common.model.Device;
import com.zhike.common.repository.OperateLog;
import com.zhike.common.utils.IpdbUtil;
import com.zhike.common.vo.RosterVO;
import com.zhike.common.vo.UserInfo4Cache;
import com.zhike.mapper.friends.FriendsMapper;
import com.zhike.mapper.system.DeviceMapper;
import com.zhike.mapper.user.UsersMapper;
import com.zhike.mobileimsdk.server.protocal.Protocal;
import com.zhike.service.messages.IACKMessageRepository;
import com.zhike.service.messages.IChatSessionService;
import com.zhike.service.system.ISystemService;
import com.zhike.utils.MessageHelper;
import com.zhike.utils.MessageHelper.SendResultObserver;
import com.zhike.utils.cache.DeviceInfoCacheProvider;
import com.zhike.utils.cache.UsersInfoCacheProvider;
import com.zhike.utils.manager.UsersStatusManager;
import com.zhike.common.enums.YesNoEnum;
import cn.hutool.json.JSONUtil;
import io.netty.channel.Channel;
import lombok.extern.slf4j.Slf4j;

/**
 * IM Server 服务器通用业务逻辑实现类（除群聊外的各种逻辑）.
 * 
 * @author Zhaocy
 */
@Slf4j
@Component
public class ChatManager {

    @Autowired
    private UsersStatusManager usersStatusManager;

    @Autowired
    private UsersInfoCacheProvider usersInfoCacheProvider;

    @Autowired
    private DeviceInfoCacheProvider deviceInfoCacheProvider;

    @Autowired
    private MessageHelper messageHelper;

    @Resource
    private UsersMapper usersMapper;

    @Resource
    private FriendsMapper friendsMapper;

    @Resource
    private DeviceMapper deviceMapper;

    @Autowired
    private ISystemService systemService;

    @Autowired
    private IACKMessageRepository ackMessageRepository;

    @Autowired
    private IChatSessionService chatSessionService;


    /**
     * 用户登录处理，通知好友上线
     * 
     * @param deviceNo 用户登录设备ID （IM登录凭证ID）
     * @param extra
     * @param session
     */
    public void processUserLoginAction(final String userUid, String extra, Channel session) {
        usersStatusManager.printLiveUsers();

        // ** 用户登陆成功后由服务端通知他的好友，他上线了，此时就把它的user_id传到服务端了！
        // 在线用户列表
        ConcurrentMap<String, Channel> liveUsers = usersStatusManager.getLiveUsers();
        try {
            UserInfo4Cache uc = usersInfoCacheProvider.get(userUid);
            List<RosterVO> roster = uc.getRosterList();

            // 遍历好友列表，看看它有哪些好友是在线的
            for (RosterVO friendRow : roster) {
                // 好友uid
                final String friend_user_uid = friendRow.getFriendUserUid();
                final String friend_user_nickname = friendRow.getNickname();

                // 遍历在线好友列表，看看该好友是否在线
                if (liveUsers.containsKey(friend_user_uid)) {
                    // 指令发送结果观察者（因为通信底层netty是异步通知数据发送结果的）
                    SendResultObserver sendResultObserver = new SendResultObserver() {
                        @Override
                        public void update(boolean code, Protocal p) {
                            if (code)
                                log.debug("[ZKIMChat]》登陆用户" + userUid + "向它的好友"
                                        + friend_user_uid + "/" + friend_user_nickname + "发送上线通知成功！");
                            else
                                log.debug("[ZKIMChat]》登陆用户" + userUid + "向它的好友"
                                        + friend_user_uid + "/" + friend_user_nickname + "发送上线通知失败，错误code=" + code);
                        }
                    };
                    // 发送消息 （发送给好友用户所有设备）
                    String message = friend_user_nickname + "上线了";
                    for (Device d : uc.getDeviceList()) {
                        messageHelper.sendOnlineMessage(d.getDeviceNo(), message, sendResultObserver);
                    }

                } else {
                    log.debug("[ZKIMChat]》登陆用户" + userUid + "的好友"
                            + friend_user_uid + "/" + friend_user_nickname + "不在线，无需发送上线通知。");
                }
            }
        } catch (Exception e) {
            log.warn(e.getMessage(), e);
        } finally {
            String phone = "";
            String deviceType = "";
            UserInfo4Cache uc = systemService.getUserInfo(userUid);
            if (uc != null) {
                phone = uc.getPhone();
                deviceType = uc.getCurrentDeviceType();
            }
            // 增加退出系统日志
            systemService.syncSaveOprateLog(new OperateLog()
                    .setPhone(phone)
                    .setIp(session.remoteAddress().toString())
                    .setOprLevel("info")
                    .setOprBz("登录IM")
                    .setOprTime(new Date())
                    .setOsType(deviceType)
                    .setIpRegion(IpdbUtil.getIpAddress(session.remoteAddress().toString())));
        }
    }

    /**
     * 用户注销后由服务端通知他的好友，他下线了！
     * 
     * @param deviceNo
     * @param session
     * @param beKickoutCode
     */
    public void processUserLogoutAction(String deviceNo, Channel session, int beKickoutCode) {
        Device device = deviceInfoCacheProvider.get(deviceNo);
        // 下线的用户，必然之前已经在在线列表里了
        if (device != null) {
            try {
                // 该用户的好友列表
                List<RosterVO> roster = friendsMapper.getRosterByDeviceNo(deviceNo);

                // 遍历好友列表，看看它有哪些好友是在线的
                for (RosterVO friendRow : roster) {
                    // 好友uid
                    final String friend_user_uid = friendRow.getFriendUserUid();
                    final String friend_user_mail = friendRow.getNickname();
                    // 自已的uid（顺便取出来备用）
                    final String user_uid = friendRow.getUserUid();

                    // FIXME: 备忘-> 因无法目前无全功能集群支持，无法取得所有im接入层的在线列表，所以
                    // 当开通与web端互通时，就无法将上、下线通知给跟机器端了。不过，在线状态
                    // 这种很难维护的东西，可以考虑像微信一样，在以后的版本中逐渐去掉，实际上来说
                    // ，用处也不是很大，只是传统IM（比如QQ）时代遗留下来的习惯而已！

                    // 看看该好友是否在线
                    // if(liveUsers.containsKey(friend_user_uid))
                    if (usersStatusManager.isOnline(friend_user_uid)) {
                        // 指令发送结果观察者（因为通信底层netty是异步通知数据发送结果的）
                        SendResultObserver sendResultObserver = new SendResultObserver() {
                            @Override
                            public void update(boolean code, Protocal p) {
                                if (code)
                                    log.info("[ZKIMChat]《登陆用户" + user_uid + "向它的好友"
                                            + friend_user_uid + "/" + friend_user_mail + "发送下线通知成功！");
                                else
                                    log.warn("[ZKIMChat]《登陆用户" + user_uid + "向它的好友"
                                            + friend_user_uid + "/" + friend_user_mail + "发送下线通知失败，错误code=" + code);
                            }
                        };
                        messageHelper.sendOfflineMessage(friend_user_uid, user_uid, sendResultObserver);
                    } else {
                        log.info("[ZKIMChat]《登陆用户" + deviceNo + "的好友"
                                + friend_user_uid + "/" + friend_user_mail + "不在线，无需发送下线通知【END】。");
                    }
                }
            } catch (Exception e) {
                log.error("IM注销通知好友发生异常 {}", e.getMessage());
            }
        } else {
            log.error("[ZKIMChat]当前正在离线的用户" + deviceNo + "为null？？？");
        }

        // 将该用户从在线用户列表中去掉
        usersStatusManager.userOffline(deviceNo, "");

        String phone = "";
        UserInfo4Cache uc = systemService.getUserInfo(device.getFakeUid());
        if (uc != null) {
            phone = uc.getPhone();
        }
        // 增加退出系统日志
        systemService.syncSaveOprateLog(new OperateLog()
                .setPhone(phone)
                .setIp(session.remoteAddress().toString())
                .setOprLevel("info")
                .setOprBz("注销IM")
                .setOprTime(new Date())
                .setOsType(device.getDeviceType())
                .setIpRegion(IpdbUtil.getIpAddress(session.remoteAddress().toString())));

        // TODO 用于debug时，生产时可以去掉哦！
        usersStatusManager.printLiveUsers();
    }

    // 处理已读消息
    public boolean processMT59_READED_MESSAGE(CMDBodyReadedMessageRequest dto) {
        try {
            // 更新群组会话@消息的数量
            if (StringUtils.isNoneBlank(dto.getGroupId())) {
                GroupAtUserForSessionDTO gu = new GroupAtUserForSessionDTO();
                gu.setGroupId(dto.getGroupId());
                gu.setIsAll(0);
                List<String> uids = new ArrayList<>();
                uids.add(dto.getFromUid());
                gu.setUserIds(uids);
                chatSessionService.updateSessionAt(gu, YesNoEnum.NO.getValue());
            } else { // 单聊
                ackMessageRepository.deleteAckMessage(dto.getToUid(), dto.getFromUid());
                // 发消息给客户端，变更未读消息状态
                MessageHelper.SendResultObserver sendResultObserver = new MessageHelper.SendResultObserver() {

                    @Override
                    public void update(boolean code, Protocal p) {
                        if (code) {
                            log.debug("[ZKIMChat]服务器通知由fromUid=" + dto.getFromUid()
                                    + "发起的读消息，已经成功更新已读消息 toUid=" + dto.getToUid() + ".");
                        }

                    }
                };

                // 服务器主动发送给客户端
                List<String> devices = deviceMapper.queryDevicesByUser(dto.getToUid());
                for (String deviceNo : devices) {
                    log.debug("服务器主动发送给客户端 {}", deviceNo);
                    messageHelper.sendMessage(deviceNo, JSONUtil.toJsonStr(dto), false,
                            UserProtocalsType.MT60_OF_MARK_READED_CALLBACK, sendResultObserver);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            log.error("{}", e.getMessage());
        }
        return false;
    }

    // 撤回消息通知
    @Deprecated
    public void processMT57_REVOKE_MESSAGE(RevokedDTO dto) {
        try {
            MessageHelper.SendResultObserver sendResultObserver = new MessageHelper.SendResultObserver() {

                @Override
                public void update(boolean code, Protocal p) {
                    if (code) {
                        log.info(" 57 ------------------ {} {} {}", code, p, p.getDataContent());
                        // CMDBody4RevokeMessageRequest tm = CMDBody4RevokeMessageRequest
                        // .parseFromSender(p.getDataContent());
                        // String sessionId = "";
                        // ChatSessionMessageDTO cs1 = new ChatSessionMessageDTO();
                        // cs1.setFromUid(tm.getFromUid());
                        // cs1.setToUid(tm.getToUid());
                        // cs1.setChatType(String.valueOf(tm.getChatType()));
                        // cs1.setMsgType(String.valueOf(tm.getMsgType()));
                        // cs1.setLastMsg(tm.getDataContent());
                        // cs1.setLastTime(new Date());
                        // cs1.setFp(tm.getFp());
                        // cs1.setSessionId(IdUtil.getSnowflakeNextIdStr());
                        // sessionId = chatSessionService.updateUserChatSession(cs1);
                        // ChatSessionMessageDTO cs2 = ObjectUtil.clone(cs1);
                        // cs2.setFromUid(tm.getToUid());
                        // cs2.setToUid(tm.getFromUid());
                        // sessionId = chatSessionService.updateUserChatSession(cs2);
                        // log.debug("[ZKIMChat]服务器通知由uid=" + dto.getFromUid()
                        // + "发起的读消息，已经成功更新已读 uid=" + dto.getFp() + ".");

                        // messageRecordManager.colectMsgAsync(sessionId, tm.getFromUid(),
                        // tm.getToUid(),
                        // Integer.valueOf(tm.getChatType()),
                        // Integer.valueOf(tm.getMsgType()),
                        // tm.getDataContent(),
                        // cs1.getFp());

                    }
                }
            };

            // 通知对方终端
            // List<String> toDevices = deviceMapper.queryDevicesByUser(dto.getToUid());
            // for (String deviceNo : toDevices) {
            //     saveRevokeMessage(deviceNo, dto, sendResultObserver);
            // }

            // // 通知自身其他终端
            // List<String> fromDevices = deviceMapper.queryDevicesByUser(dto.getFromUid());
            // for (String deviceNo : fromDevices) {
            // saveRevokeMessage(deviceNo, dto, sendResultObserver);
            // }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 撤回消息处理
    private void saveRevokeMessage(String deviceNo, RevokedDTO dto, MessageHelper.SendResultObserver sendResultObserver)
            throws Exception {
        CMDBody4RevokeMessageRequest revoke = new CMDBody4RevokeMessageRequest();
        // revoke.setFromUid(dto.getFromUid());
        // revoke.setToUid(dto.getToUid());
        revoke.setFp(dto.getFp());
        revoke.setChatType("0");
        revoke.setMsgType("11");
        revoke.setDataContent("撤销了一条消息");
        messageHelper.sendMessage(deviceNo, JSONUtil.toJsonStr(revoke), true,
                UserProtocalsType.MT57_OFF_REVOKE,
                sendResultObserver);

    }

}
